﻿#ifdef _DEBUG
#include <csignal>
#endif  // DEBUG

#include <Windows.h>
#include <thread>

#include "../Utils/Utils.h"
#include "../Utils/Logger.h"

#include "../Memory/GameData.h"
// #include "../Memory/MinHook.h"
#include "../Memory/Hook.h"

#include "../Utils/DrawUtils.h"
#include "Config/ConfigManager.h"
#include "Setting/SettingManager.h"
#include "Modules/ModulesManager.h"
#include "Commands/CommandsManager.h"

bool isRunning = true;
bool advanceExit = false;

void helium_cleanmemory() {        // clean memory
	if (isRunning) [[unlikely]] {  // This will only be called if there is a crash
		isRunning = false;
	}
	static bool hasclean = false;
	if (!hasclean) [[likely]] {
		hasclean = true;

		// eject ...
		ModuleManager::disable();
		CommandMgr::disable();
		configMgr.saveConfig();
		SettingManager::exit();

		Hooks::Restore();
		MH_Uninitialize();
#ifndef _GHOST
		if (GameData::isInGame()) {
			switch (SettingManager::languageget) {
			case 0:
			default:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::YELLOW,
					u8"Eject!");
				break;
			case 1:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::YELLOW,
					u8"卸载成功!");
				break;
			case 2:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::YELLOW,
					u8"卸載成功!");
				break;
			case 3:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::YELLOW,
					u8"アンインストールに成功しました！");
				break;
				std::unreachable();
			}
		}
#endif  // !_GHOSTe
	}
}

void Helium_exit() {  // clean memory and exit
	static bool hasexit = false;
	if (!hasexit) [[likely]] {
		hasexit = true;
		// clean memory
		helium_cleanmemory();  // 1
		// disbale logger
		Logger::LogC(LogGrade::info, u8"Eject!");
		Logger::Disable();
		if (advanceExit) [[unlikely]] {  // advance exit
										 // do sth..............................
		}
	}
}

DWORD WINAPI keyThread(LPVOID lpParam) {
	Logger::LogC(LogGrade::info, u8"Key thread started");
	bool keyMap[0xFF * 4 + 0x4]{};

	bool* keyMapAddr = nullptr;
	{
		const uintptr_t sigOffset = Utils::Utils::getBase() + 7990851;
		const int offset = *reinterpret_cast<int*>((sigOffset + 3));                             // Get Offset from code
		keyMapAddr = reinterpret_cast<bool*>(sigOffset + offset + /*length of instruction*/ 7);  // Offset is relative
	}

	while (isRunning) {
		if ((GameData::isKeyDown('L') && GameData::isKeyDown(VK_CONTROL)) || GameData::shouldTerminate()) {  // Press L to uninject
			isRunning = false;
			advanceExit = GameData::isKeyDown(VK_MENU);  // ALT
			break;
		}

		for (uint8_t i = 0; i < 0xFFui8; i++) {
			bool* newKey = keyMapAddr + (4 * i);
			bool newKeyPressed = (*newKey) && GameData::canUseMoveKeys();  // Disable Keybinds when in chat or inventory
			bool* oldKey = keyMap + (4 * i);
			if (newKeyPressed != *oldKey) {
				ModuleManager::onKeyUpdate(i, newKeyPressed); 
				// do sth..............................
			}
			if (*newKey != *oldKey) {  // Skip Chat or inventory checks
				// TabGui::onKeyUpdate(i, *newKey);
				// ClickGui::onKeyUpdate(i, *newKey); 
				// do sth..............................
			}
		}
		fast_io::freestanding::my_memcpy(keyMap, keyMapAddr, static_cast<rsize_t>(0xFF * 4));

		Sleep(2);
		// std::this_thread::sleep_for(std::chrono::milliseconds(2));
	}
	// clean memory and eject
	Helium_exit();                                               // 1
	FreeLibraryAndExitThread(static_cast<HMODULE>(lpParam), 0);  // eject
}

DWORD WINAPI start(LPVOID lpParam) {
	Logger::LogC(LogGrade::info, u8"Injecting...");
	Logger::LogC(LogGrade::info, u8"Local Timezone: ", fast_io::native_local_timezone{}());
	Logger::LogC(LogGrade::info, Utils::Utils::ClientName, u8" V", Utils::Utils::Version.f, fast_io::mnp::chvw(u8'.'), Utils::Utils::Version.s);

	const auto start_inject_time{fast_io::posix_clock_gettime(fast_io::posix_clock_id::realtime)};  // get real time
	// write file
	//  od .....

	// creat key thread
	CreateThread(nullptr, NULL, (LPTHREAD_START_ROUTINE)keyThread, lpParam, NULL, nullptr);  // key thread

	// init
	const auto clientinstance = GameData::retrieveClientInstance();
	Logger::LogC(LogGrade::info, u8"Client Instance: ", fast_io::mnp::addrvw(clientinstance));

	SettingManager::load();

	DrawUtils::init();

	Hooks::Init();

	// do something
	ModuleManager::initModules();
	CommandMgr::initCommands();
	if (!configMgr.loadConfig(SettingManager::lastConfig)) {
		Logger::LogC(LogGrade::warning, u8"Failed to read the last configuration! So the default config is created.");
		configMgr.createConfig(u8"default"); // create and save
		if (GameData::getLocalPlayer() != nullptr) {
			switch (SettingManager::languageget) {
			case 0:
			default:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::WHITE,
					u8"Loading Config: ",
					Utils::TextColor::YELLOW,
					u8"default",
					Utils::TextColor::WHITE,
					u8" succeeded!");
				break;
			case 1:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::WHITE,
					u8"导入配置： ",
					Utils::TextColor::YELLOW,
					u8"default",
					Utils::TextColor::WHITE,
					u8" 成功！");
				break;
			case 2:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::WHITE,
					u8"導入配置： ",
					Utils::TextColor::YELLOW,
					u8"default",
					Utils::TextColor::WHITE,
					u8" 成功！");
				break;
			case 3:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::WHITE,
					u8"構成 ",
					Utils::TextColor::YELLOW,
					u8"default",
					Utils::TextColor::WHITE,
					u8" を正常にインポート！");
				break;
				std::unreachable();
			}
		}
	} else {
		if (GameData::isInGame()) {
			switch (SettingManager::languageget) {
			case 0:
			default:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::WHITE,
					u8"Loading Config: ",
					Utils::TextColor::YELLOW,
					SettingManager::lastConfig,
					Utils::TextColor::WHITE,
					u8" succeeded!");
				break;
			case 1:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::WHITE,
					u8"导入配置： ",
					Utils::TextColor::YELLOW,
					SettingManager::lastConfig,
					Utils::TextColor::WHITE,
					u8" 成功！");
				break;
			case 2:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::WHITE,
					u8"導入配置： ",
					Utils::TextColor::YELLOW,
					SettingManager::lastConfig,
					Utils::TextColor::WHITE,
					u8" 成功！");
				break;
			case 3:
				C_GuiData::displayClientMessageC(
					Utils::TextColor::LIGHT_PURPLE,
					u8"[Helium] ",
					Utils::TextColor::WHITE,
					u8"構成 ",
					Utils::TextColor::YELLOW,
					SettingManager::lastConfig,
					Utils::TextColor::WHITE,
					u8" を正常にインポート！");
				break;
				std::unreachable();
			}
		}
	}
	Hooks::Enable();
	// ...
	Logger::LogC(LogGrade::info, u8"Successful Injection, takes ", fast_io::posix_clock_gettime(fast_io::posix_clock_id::realtime) - start_inject_time, fast_io::mnp::chvw(u8's'));
#ifndef _GHOST
	if (GameData::getLocalPlayer() != nullptr) {
		switch (SettingManager::languageget) {
		case 0:
		default:
			C_GuiData::displayClientMessageC(
				Utils::TextColor::LIGHT_PURPLE,
				u8"[Helium] ",
				Utils::TextColor::YELLOW,
				u8"Inject!");	
			break;
		case 1:
			C_GuiData::displayClientMessageC(
				Utils::TextColor::LIGHT_PURPLE,
				u8"[Helium] ",
				Utils::TextColor::YELLOW,
				u8"注入成功！");	
			break;
		case 2:
			C_GuiData::displayClientMessageC(
				Utils::TextColor::LIGHT_PURPLE,
				u8"[Helium] ",
				Utils::TextColor::YELLOW,
				u8"註入成功！");	
			break;
		case 3:
			C_GuiData::displayClientMessageC(
				Utils::TextColor::LIGHT_PURPLE,
				u8"[Helium] ",
				Utils::TextColor::YELLOW,
				u8"正常に注入されました！");	
			break;
			std::unreachable();
		}		
	}
#endif
#if 1
	std::thread([]() {  // logger refresh thread
		while (isRunning) [[likely]] {
			Logger::Refresh();
#ifdef _DEBUG
			Sleep(500);
			// std::this_thread::sleep_for(std::chrono::milliseconds(500));  // Refresh (0.5s)
#else
			Sleep(3000);
			// std::this_thread::sleep_for(std::chrono::seconds(3));  // Refresh (3s)
#endif  // _DEBUG
		}
	}).detach();
#endif  // 1

	std::thread([]() { // onUpdate
		while (isRunning) [[likely]] {
			ModuleManager::onUpdate(); 
			Sleep(10);
		}
	}).detach();

	ExitThread(0);
}

#ifdef _DEBUG
#ifndef HELIUM_USE_SIGNUM
void exceptionHandler(PEXCEPTION_POINTERS excpInfo) {  // crash
	Logger::AbortSignal();
}

LONG WINAPI unhandledException(PEXCEPTION_POINTERS excpInfo = nullptr) {
	if (excpInfo == nullptr) {
		__try {
			RaiseException(EXCEPTION_BREAKPOINT, 0, 0, nullptr);
		} __except (exceptionHandler(GetExceptionInformation()),
					EXCEPTION_EXECUTE_HANDLER) {
		}
	} else {
		exceptionHandler(excpInfo);
	}

	return 0;
}
#endif
#endif  // _DEBUG

BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpvoid) {
	switch (ul_reason_for_call) {
	case DLL_PROCESS_ATTACH: {
		// signal
#ifdef _DEBUG
#ifdef HELIUM_USE_SIGNUM
		signal(SIGINT, Logger::AbortSignal);
		signal(SIGILL, Logger::AbortSignal);
		signal(SIGFPE, Logger::AbortSignal);
		signal(SIGSEGV, Logger::AbortSignal);
		signal(SIGTERM, Logger::AbortSignal);
		signal(SIGBREAK, Logger::AbortSignal);
		signal(SIGABRT, Logger::AbortSignal);
#else
		SetUnhandledExceptionFilter(unhandledException);
#endif  // 0
#endif
		// Create Directory
#if 0
		CreateDirectoryW(fast_io::wconcat(fast_io::mnp::code_cvt(Utils::Utils::RoamingStateFold)).data(), nullptr);                      // RoamingStateFold
		CreateDirectoryW(fast_io::wconcat(fast_io::mnp::code_cvt(Utils::Utils::RoamingStateFold), L"Logs\\").data(), nullptr);           // Logs
		std::wstring configD = fast_io::wconcat(fast_io::mnp::code_cvt(Utils::Utils::RoamingStateFold), L"Configs\\");
		CreateDirectoryW(configD.data(), nullptr);                                                                                       // Configs
		ConfigManager::configDirectory = fast_io::dir_file(configD);
		CreateDirectoryW(fast_io::wconcat(fast_io::mnp::code_cvt(Utils::Utils::RoamingStateFold), L"Structures\\").data(), nullptr);     // Easy Place Structure
		CreateDirectoryW(fast_io::wconcat(fast_io::mnp::code_cvt(Utils::Utils::RoamingStateFold), L"resource\\").data(), nullptr);       // WAV, data, etc.
		CreateDirectoryW(fast_io::wconcat(fast_io::mnp::code_cvt(Utils::Utils::RoamingStateFold), L"resource\\wav\\").data(), nullptr);  // WAV
		CreateDirectoryW(fast_io::wconcat(fast_io::mnp::code_cvt(Utils::Utils::RoamingStateFold), L"resource\\png\\").data(), nullptr);  // PNG
		CreateDirectoryW(fast_io::wconcat(fast_io::mnp::code_cvt(Utils::Utils::RoamingStateFold), L"resource\\csc\\").data(), nullptr);  // CSC
		CreateDirectoryW(fast_io::wconcat(fast_io::mnp::code_cvt(Utils::Utils::RoamingStateFold), L"resource\\player_behavior\\").data(), nullptr);  // CSC

#else
		const auto ntfdcwd{fast_io::nt_at_fdcwd()};
		fast_io::native_mkdirat(ntfdcwd, Utils::Utils::RoamingStateFold);
		fast_io::native_mkdirat(ntfdcwd, fast_io::u8concat(Utils::Utils::RoamingStateFold, u8"Logs\\"));
		const std::u8string configD{fast_io::u8concat(Utils::Utils::RoamingStateFold, u8"Configs\\")};
		fast_io::native_mkdirat(ntfdcwd, configD);
		ConfigManager::configDirectory = fast_io::dir_file(configD);
		fast_io::native_mkdirat(ntfdcwd, fast_io::u8concat(Utils::Utils::RoamingStateFold, u8"Structures\\"));
		fast_io::native_mkdirat(ntfdcwd, fast_io::u8concat(Utils::Utils::RoamingStateFold, u8"resource\\"));
		fast_io::native_mkdirat(ntfdcwd, fast_io::u8concat(Utils::Utils::RoamingStateFold, u8"resource\\wav\\"));
		fast_io::native_mkdirat(ntfdcwd, fast_io::u8concat(Utils::Utils::RoamingStateFold, u8"resource\\png\\"));
		fast_io::native_mkdirat(ntfdcwd, fast_io::u8concat(Utils::Utils::RoamingStateFold, u8"resource\\csc\\"));
		const std::u8string playerBehDir{fast_io::u8concat(Utils::Utils::RoamingStateFold, u8"resource\\player_behavior\\")};
		fast_io::native_mkdirat(ntfdcwd, playerBehDir);
		PlayerBehavior_Dir = fast_io::dir_file(playerBehDir);
#endif  // 0

		// Enable Logger
		Logger::Enable();  // The directory needs to be built in advance

		// Minecraft Module
		const auto proc = GetCurrentProcess();
		K32GetModuleInformation(proc, (HMODULE)Utils::Utils::CachedBase, &Utils::Utils::Minecraft_MODULEINFO, sizeof(MODULEINFO));
		// Helium Module
		Utils::Utils::helium_HMODULE = hModule;
		K32GetModuleInformation(proc, Utils::Utils::helium_HMODULE, &Utils::Utils::helium_MODULEINFO, sizeof(MODULEINFO));
		// LPVOID
		Utils::Utils::helium_LPVOID = lpvoid;
		// MinHook Init
		MH_Initialize();  // must init before hook init
						  // Inject
#if 1
		CreateThread(nullptr, NULL, (LPTHREAD_START_ROUTINE)start, hModule, NULL, nullptr);
#else
		CreateThread(nullptr, NULL, (LPTHREAD_START_ROUTINE)test, hModule, NULL, nullptr);
#endif

	} break;
	case DLL_PROCESS_DETACH: {
	} break;
	}
	return TRUE;
}

/*something to do
 * KeyThread
 * GameData::forAllEntity...
 */